/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.servlet.book.epub;

import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import offset.nodes.client.model.SaxUtil;
import offset.nodes.client.virtual.model.jcr.PrintHandler;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

/**
 * Represents the table of contents in an epub book.
 * 
 * @author Walter L�tgenau
 */
public class Toc {
    String title;
    String id;

    /**
     * A entry in the table of contents, including its child entries
     */
    class TocEntry {
        String name;
        String href;
        TocEntry parent;
        List<TocEntry> children = new ArrayList<TocEntry>();

        public TocEntry(String name, String href) {
            this.name = name;
            this.href = href;
        }

        public void addChild(TocEntry child) {
            children.add(child);
            child.setParent(this);
        }

        public TocEntry getParent() {
            return parent;
        }

        public void setParent(TocEntry parent) {
            this.parent = parent;
        }

        public List<TocEntry> getChildren() {
            return children;
        }

        public String getHref() {
            return href;
        }

        public String getName() {
            return name;
        }

        /**
         * The level of the entry in the content hierarchy. Start at 1.
         *
         * @return the level
         */
        public int getLevel() {
            if (parent == null)
                return 0;

            int level = 0;

            TocEntry entry = this;
            while(entry != root) {
                level++;
                entry = entry.getParent();
            }

            return level;
        }
    }

    TocEntry root = new TocEntry(null, null);
    TocEntry current;

    /**
     * Add an entry to the table of contents.
     *
     * @param name the text for the entry
     * @param href the href attribute of the opf file
     * @param level the level of the entry. Highest level is 1.
     */
    public void add(String name, String href, int level) {
        if (level < 0)
            return;
        
        TocEntry entry = new TocEntry(name, href);
        if (current == null ) {
            root.addChild(entry);
            current = entry;
            return;
        }

        int currentLevel = current.getLevel();
        if (currentLevel > level) { // add to ancestor
            for (int i = 0; i <= currentLevel - level; i++)
                current = current.getParent();
        } else if (currentLevel == level) { // add to parent
            current = current.getParent();
        } // else add to current
        
        current.addChild(entry);
        current = entry;
    }

    /**
     * Write the table of content to the output stream
     * 
     * @param outputStream
     */
    public void write(OutputStream outputStream) throws SAXException {
        try {
            ContentHandler handler = new PrintHandler(outputStream);
            SaxUtil writer = new SaxUtil(handler);

            AttributesImpl atts = new AttributesImpl();
            writer.addAttribute(atts, "xmlns", "http://www.daisy.org/z3986/2005/ncx/");
            writer.addAttribute(atts, "version", "2005-1");
            writer.addAttribute(atts, "xml:lang", "en-EN");

            writer.startElement("ncx", atts);

            writer.startElement("head");

            atts = new AttributesImpl();
            writer.addAttribute(atts, "name", "dc:title");
            writer.addAttribute(atts, "content", title);
            writer.startElement("meta", atts);
            writer.endElement("meta");

            atts = new AttributesImpl();
            writer.addAttribute(atts, "name", "dtb:uid");
            writer.addAttribute(atts, "content", id);
            writer.startElement("meta", atts);
            writer.endElement("meta");

            writer.endElement("head");

            writer.startElement("docTitle");
            writer.startElement("text");
            writer.characters(title);
            writer.endElement("text");
            writer.endElement("docTitle");

            writer.startElement("navMap");
            for(TocEntry entry: root.getChildren())
                writeEntry(entry, writer);
            writer.endElement("navMap");

            writer.endElement("ncx");
        } catch (UnsupportedEncodingException ex) {
            throw new SAXException(ex);
        }
    }

    /**
     * Write a nav point and all its sub nav points.
     *
     * @param entry the entry to be written
     * @param writer the writer to be used
     * @throws SAXException
     */
    protected void writeEntry(TocEntry entry, SaxUtil writer) throws SAXException {
        if (entry == null || entry.getName() == null || entry.getHref() == null)
            return;
        
        AttributesImpl atts = new AttributesImpl();
        String order = getOrder();
        writer.addAttribute(atts, "id", order);
        writer.addAttribute(atts, "playOrder", order);

        writer.startElement("navPoint", atts);

        writer.startElement("navLabel");
        writer.startElement("text");
        writer.characters(entry.getName());
        writer.endElement("text");
        writer.endElement("navLabel");

        atts = new AttributesImpl();
        writer.addAttribute(atts, "src", entry.getHref());
        writer.startElement("content", atts);
        writer.endElement("content");

        for (TocEntry child : entry.getChildren())
            writeEntry(child, writer);

        writer.endElement("navPoint");
    }

    int order = 1;
    protected String getOrder() {
          return "" + order++;
    }

    /**
     * Has the title and the name been set?
     *
     * @return
     */
    public boolean isInitialized() {
        return title != null && id != null;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setTitle(String title) {
        this.title = title;
    }

}
